package pt.inescid.components.policyengine.heimdall.core;

import java.util.Vector;

import pt.inescid.components.policyengine.heimdall.base.EventObj;
import pt.inescid.components.policyengine.heimdall.base.User;
import pt.inescid.components.policyengine.heimdall.commons.Log;
import pt.inescid.components.policyengine.heimdall.commons.ProjectInfo;
import pt.inescid.components.policyengine.heimdall.commons.TimeUtilities;
import pt.inescid.components.policyengine.heimdall.commons.Utilities;
import pt.inescid.components.policyengine.heimdall.contact.Inbound;
import pt.inescid.components.policyengine.heimdall.contact.RemoteCommand;
import pt.inescid.components.policyengine.heimdall.pap.UserManagerAS;
import pt.inescid.components.policyengine.heimdall.pap.base.CesDefinition;
import pt.inescid.components.policyengine.heimdall.pap.base.ModelDefinition;
import pt.inescid.components.policyengine.heimdall.pdp.PolicyDecision;
import pt.inescid.components.policyengine.heimdall.pdp.base.ModelInstance;
import pt.inescid.components.policyengine.heimdall.pdp.base.PurgeInstance;
import pt.inescid.components.policyengine.heimdall.pdp.base.ReevaluationInstance;
import pt.inescid.components.policyengine.heimdall.pip.PolicyPIP;

public class HeimdallServer extends Thread {

	public static HeimdallServer instance = null;
	
	public PolicyPIP pip = PolicyPIP.getInstance();

	public Inbound inbound = null;
	
	
	public Vector receptionQueue = null;

	public Vector eventsInProcess = null;
	public Vector<ModelInstance> eventsInLease = null;
	public Vector eventsInReevaluation = null;
	public Vector eventsInObligation = null;
	public Vector processedEvents = null;

	private boolean stopTag = false;

	public static final long LEASE_TIME = 100000;
	public static final long OBLIGATION_TIMEOUT = 1000;
	public static final long OBLIGATION_INFO_RETRIEVAL_TIMEOUT = 2000;

	public static final int NEW_REMOTE_INFO = 0;
	public static final int NEW_REMOTE_EVALUATION = 1;

	
	/* ===========================================================
	==============================================================
	==============================================================
	constructor
	==============================================================
	==============================================================
	=========================================================== */
	private HeimdallServer() {
	//	inbound = new Inbound(
	//			ProjectInfo.getProperty(ProjectInfo.HOST_NAME_PROPERTY),
	//			new Integer(ProjectInfo.getProperty(
	//					ProjectInfo.HOST_PORT_PROPERTY)).intValue());
		inbound = new Inbound(ProjectInfo.HOST_NAME_PROPERTY,
				Integer.parseInt(ProjectInfo.HOST_PORT_PROPERTY));
		inbound.start();
	}

	
	
	
	
	/* ===========================================================
	==============================================================
	==============================================================
	getInstance
	==============================================================
	==============================================================
	=========================================================== */
	public static HeimdallServer getInstance() {
		if (instance == null) {
			instance = new HeimdallServer();
			QueueManager.initQueues();
			instance.start();
			
			Log.debug(Log.DEBUG, "Started new instance",
					"HeimdallServer.getInstance");
		}
		
		return (instance);
	} // getInstance
	

	
	/* ===========================================================
	==============================================================
	=============                    =============================
	=============      getAllLeaseEvents       =============================
	=============                    =============================
	==============================================================
	=========================================================== */
	public Vector getAllLeaseEvents(String ModelDefTag) {

		Vector result = new Vector();
		ModelInstance thisModel = null;
		synchronized (eventsInLease) {
			for (int i = 0; i < eventsInLease.size(); i++) {
				thisModel = (ModelInstance) eventsInLease.
						elementAt(i);
				
				if (thisModel.getDefinition().getTag().equals(
						ModelDefTag)) {
					
					result.add(thisModel.getCe());
				}
			}
		}
		
		return (result);
	} // getAllLeaseEvents
	
	
	
	/* ===========================================================
	==============================================================
	==============================================================
	finishLease
	==============================================================
	==============================================================
	=========================================================== */
	public void finishLease(User currentUser, String modelTag, EventObj ce) {
	
		Log.debug(Log.INFO, "Arrived.", 
				"HeimdallServer.finishLease");

		/* ===========================================================
		=============      Authentication  =============================
		=========================================================== */
				
		/* ===========================================================
		=============      Is this entity authorized to 
		=============       use Heimdall?  =============================
		=========================================================== */

		ModelInstance currentModel = null;

		synchronized (eventsInLease) {
			for (int i = 0; i < eventsInLease.size(); i++) {
				currentModel = (ModelInstance)eventsInLease.elementAt(i);
				
				if (currentModel.getUserInfo().getId() ==
					currentUser.getId() &&
					currentModel.getDefinition().getTag().
							equals(modelTag) &&
					currentModel.getCe().getId() ==
						ce.getId()) {
					
					QueueManager.addToQueue(
							currentModel,
							QueueManager.PROCESSED_EVENTS_QUEUE);
					
					Log.debug(Log.INFO, "LEASE RELEASED Model into PROCESSED_EVENTS_QUEUE.", 
					"HeimdallServer.main");
					
					eventsInLease.removeElementAt(i);
					i = eventsInLease.size();
				}
			}
		}
	}; // finishLease
	
	/* ===========================================================
	==============================================================
	==============================================================
	registerRemoteCommandRequest
	==============================================================
	==============================================================
	=========================================================== */
	public void registerRemoteCommandRequest(RemoteCommand rc) {
	
		Log.debug(Log.INFO, "Arrived.", "HeimdallServer.registerRemoteCommandRequest");

		ReceptionQueueNode thisNode = 
			new ReceptionQueueNode(ReceptionQueueNode.REQUEST_REMOTE_COMMAND, rc);
		QueueManager.addToQueue(thisNode, 
				QueueManager.RECEPTION_QUEUE);
		
		synchronized (HeimdallServer.getInstance()) {
			HeimdallServer.getInstance().notify();
		}
	}; // registerRemoteCommandRequest

		
	/* ===========================================================
	==============================================================
	==============================================================
	registerProcessEventRequest
	==============================================================
	==============================================================
	=========================================================== */
	public void registerProcessEventRequest(User currentUser, 
			String modelDefTag, EventObj ce) {
	
		Log.debug(Log.INFO, "Arrived.", "HeimdallServer.processEvent");
		
		ModelInstance currentModel = 
			currentUser.getModelInstance(ce, modelDefTag);
		
		ReceptionQueueNode thisNode = 
			new ReceptionQueueNode(ReceptionQueueNode.REQUEST_EVALUATE_EVENT, 
					currentModel);
		QueueManager.addToQueue(thisNode, 
				QueueManager.RECEPTION_QUEUE);
		
		synchronized (HeimdallServer.getInstance()) {
			HeimdallServer.getInstance().notify();
		}
	}; // registerProcessEventRequest

	
	
	
	
	
	/* ===========================================================
	==============================================================
	==============================================================
	registerStoreEventRequest
	==============================================================
	==============================================================
	=========================================================== */
	public void registerStoreEventRequest(User currentUser, 
			String modelDefTag, EventObj ce) {
	
		Log.debug(Log.INFO, "Arrived.", "HeimdallServer.storeEvent");

		Vector newEventInfo = new Vector();
		newEventInfo.add(ce);
		newEventInfo.add(currentUser);
		newEventInfo.add(modelDefTag);
		
		/* ===========================================================
		=============      Authentication  =============================
		=========================================================== */
				
		/* ===========================================================
		=============      Is this entity authorized to 
		=============       use Heimdall?  =============================
		=========================================================== */
		
		ReceptionQueueNode thisNode = 
			new ReceptionQueueNode(ReceptionQueueNode.REQUEST_STORE_EVENT, 
					newEventInfo);
		QueueManager.addToQueue(thisNode, 
				QueueManager.RECEPTION_QUEUE);
		
		synchronized (HeimdallServer.getInstance()) {
			HeimdallServer.getInstance().notify();
		}
	}; // registerStoreEventRequest


	

	
	
	
	/* ===========================================================
	==============================================================
	==============================================================
	registerNewInfoNotification
	==============================================================
	==============================================================
	=========================================================== */
	public void registerNewInfoNotification(RemoteCommand command) {
			
			
			
//			String instanceTag,
//			String policyName,
//			String domainName,
//			String infoNodeName,
//			int infoNodePort,
//			Vector allInfo) {
	
		Log.debug(Log.INFO, "Arrived.", 
				"HeimdallServer.registerPolicyEvaluationNotification");

		ReceptionQueueNode thisNode = 
			new ReceptionQueueNode(ReceptionQueueNode.REQUEST_PROCESS_NEW_INFO, 
					command);
		QueueManager.addToQueue(thisNode, 
				QueueManager.RECEPTION_QUEUE);
		
		synchronized (HeimdallServer.getInstance()) {
			HeimdallServer.getInstance().notify();
		}
	}; // registerNewInfoNotification

	

    
    
    
    /* ===========================================================
	==============================================================
	=============                    =============================
	=============      run       =============================
	=============                    =============================
	==============================================================
	=========================================================== */
	public void run() {
		
		Log.debug(Log.INFO, "Arrived.", "HeimdallServer.main");

		ModelInstance currentModel = null;
		boolean someAction = false; 
		
		long currentTime = Utilities.UNDEFINED_LONG_VALUE;
		long waitTime = Long.MAX_VALUE;
		
		HeimdallServer server = getInstance();

		while (someAction || !stopTag || 
				eventsInObligation.size() != 0 ||
				eventsInReevaluation.size() != 0 ||
				eventsInProcess.size() != 0) {
			someAction = false;
			

			/* ===========================================================
			=============      Check Requests  ====================
			=========================================================== */
			someAction = PolicyMonitor.checkRequests();
			

			/* ===========================================================
			=============      Check Obligations  ====================
			=========================================================== */
			PolicyMonitor.checkObligations();
			
			
			/* ===========================================================
			=============      Check Reevaluations  ====================
			=========================================================== */
			PolicyMonitor.checkReevaluations();
			
			
			/* ===========================================================
			=============      Check Purges  ====================
			=========================================================== */
			//FIXME
			//PolicyMonitor.checkPurges();
			
			
			/* ===========================================================
			 =============      Finally free the processor if 
			 					anything was finished ===============
			 =========================================================== */
			if (!someAction && !stopTag) {
				// if there is a leased event, check
				// the amount of time to wait
				
				currentModel = (ModelInstance) QueueManager.getNextFromQueue(
						QueueManager.EVENTS_IN_LEASE_QUEUE, 
						false);
				
				currentTime = TimeUtilities.getCurrentTime();
				
				while (currentModel != null &&
						currentTime > 
						currentModel.getLeaseTimestamp()) {
					
					currentModel = (ModelInstance) QueueManager.getNextFromQueue(
							QueueManager.EVENTS_IN_LEASE_QUEUE, 
							true);
					
					QueueManager.addToQueue(
							currentModel,
							QueueManager.PROCESSED_EVENTS_QUEUE);
					
					Log.debug(Log.INFO, "LEASE EXPIRED Model into PROCESSED_EVENTS_QUEUE.", 
					"HeimdallServer.main");
					
					currentModel = (ModelInstance) QueueManager.getNextFromQueue(
							QueueManager.EVENTS_IN_LEASE_QUEUE, 
							false);
					
					currentTime = 
						TimeUtilities.getCurrentTime();
				}
				
				if (currentModel != null) {
					waitTime = 
						currentModel.getLeaseTimestamp() -
						currentTime;
				}
				
				// check if any obligation ends sooner
				for (int i = 0; i < eventsInObligation.size(); i++) {
					currentModel = (ModelInstance) eventsInObligation.
							elementAt(i);
					
					if (currentModel.getDecision().getStatus() ==
						PolicyDecision.STATUS_WAIT_OBLIG &&
						
							waitTime > (
							currentModel.getObligationFulfillTimestamp()-
							currentTime)) {
						
						waitTime = 
							currentModel.getObligationFulfillTimestamp()-
							currentTime;
					}
					else if (currentModel.getDecision().getStatus() ==
						PolicyDecision.STATUS_WAIT_OBLIG_INFO &&
						
							waitTime > (
							currentModel.getObligationInfoRetrievalTimestamp()-
							currentTime)) {
						
						waitTime = 
							currentModel.getObligationInfoRetrievalTimestamp()-
							currentTime;
					}
				}
				
				// check if any reevaluation ends sooner
				ReevaluationInstance currentReevalInst = null;
				long reevalWaitTime = 0;
				for (int i = 0; i < eventsInReevaluation.size(); i++) {
					currentReevalInst = (ReevaluationInstance) 
							eventsInReevaluation.
							elementAt(i);
					
					reevalWaitTime = 
						(currentReevalInst.getLastReevaluationTime() +
						currentReevalInst.getEvery()) -
						currentTime;

					if (waitTime > reevalWaitTime) {
						
						waitTime = reevalWaitTime;
					}
				}
				
/* FIXME
				// check if any purging should start sooner
				long purgeWaitTime = 0;
				Vector allUsers = UserManagerAS.getAllUsers();
				User currentUser = null;
				for (int i = 0; i < allUsers.size(); i++) {
					currentUser = (User) allUsers.elementAt(i);
					
					Vector allModelDefs = currentUser.getModels();
					ModelDefinition currentModelDef = null;
					for (int j = 0; j < allModelDefs.size(); j++) {
						currentModelDef = (ModelDefinition) 
								allModelDefs.elementAt(i);
						
						Vector allCes = currentModelDef.getCes();
						CesDefinition currentCesDef = null;
						for (int p = 0; p < allCes.size(); p++) {
							currentCesDef = (CesDefinition) 
									allCes.elementAt(p);
							
							PurgeInstance currentPurgeInst =
								currentCesDef.getPurge();
							
							if (currentPurgeInst != null) {
							
								purgeWaitTime = 
									(currentPurgeInst.getLastPurgeTime() +
											currentPurgeInst.getEvery()) -
											currentTime;

								if (waitTime > purgeWaitTime) {
									
									waitTime = purgeWaitTime;
								}
							}
						}
					}
				}
*/
				Log.debug(Log.INFO, "Going into wait mode.", 
				"HeimdallServer.main");
				
				synchronized (server) {
					server.notify();
					try {
						
						if (QueueManager.getNextFromQueue(
								QueueManager.RECEPTION_QUEUE, false) == null) {
							if (waitTime == Long.MAX_VALUE) {
								
								server.wait();
							}
							else if (waitTime > 0){
								server.wait(waitTime);
							}
						}
						
						someAction = true;
						
					} catch (InterruptedException e) {
						Log.debug(Log.INFO, "Interrupted.", 
						"HeimdallServer.main");
					}
				}
				
				Log.debug(Log.INFO, "Waking up.", 
				"HeimdallServer.main");
			
			} // if (!someAction && !stopTag)
		} // while (someAction || !stopTag) {
		
		stopInstance();
	} // main


	
	
	
	
	/* ===========================================================
	==============================================================
	==============================================================
	toString
	==============================================================
	==============================================================
	=========================================================== */
	public String toString() {
		String status = "";
		
		ReceptionQueueNode thisNode = null;
		for (int i = 0; i < receptionQueue.size(); i++) {
			thisNode = (ReceptionQueueNode) receptionQueue.elementAt(i);
			
			status += "" + i + ": " + 
					ReceptionQueueNode.REQUEST_CODES[thisNode.type] + "\n";
		}
		
		status += "In Process[" + eventsInProcess.size() + "].";
		status += "Lease[" + eventsInLease.size() + "].";
		status += "Oblig[" + eventsInObligation.size() + "].";
		status += "Done[" + processedEvents.size() + "].\n";
		
		
		status += "----------------------------------";
		return (status);
	}; // toString


	/* ===========================================================
	==============================================================
	==============================================================
	finalize
	==============================================================
	==============================================================
	=========================================================== */
	public void finalize() {
		Log.debug(Log.INFO, "Finalizing.", "HeimdallServer.finalize");

		if (instance != null &&
				instance.inbound != null) {

			instance.inbound.stopInstance();
		}
		else {
			Log.debug(Log.INFO, "inbound is null.", "HeimdallServer.finalize");
		}
		
		synchronized (this) {
			this.stopTag = true;
			this.notify();
		}
		
		instance = null;
	};

	
	/* ===========================================================
	==============================================================
	==============================================================
	stopInstance
	==============================================================
	==============================================================
	=========================================================== */
	public static void stopInstance() {
		Log.debug(Log.INFO, "Finalizing.", "HeimdallServer.stopInstance");

		if (instance != null &&
				instance.inbound != null) {

			instance.inbound.stopInstance();
		}
		else {
			Log.debug(Log.INFO, "inbound is null.", "HeimdallServer.finalize");
		}
		
		synchronized (instance) {
			instance.stopTag = true;
			instance.notify();
		}
		
		instance = null;
	}; // stopInstance

	
	/* ===========================================================
	==============================================================
	==============================================================
	stopInstance
	==============================================================
	==============================================================
	=========================================================== */
    public static boolean isRunning() {
    	return (instance != null &&
    			instance.inbound.isRunning());
    }


	public void setStopTag(boolean stopTag) {
		this.stopTag = stopTag;
	}
	

	
} // HeimdallServer
